/* Author: Sabuj Pattanayek
 * Purpose: Send a double in an MPI message around N processes. As a command line
 * argument the number of times the message is sent around the ring is specified
 * with -n and the average time is calculated and outputted. If -n is not specified
 * the number of times defaults to 10.
 * Usage: test_ringtest -n intNumberOfTimesToCirculateMessage
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <mpi.h>

int main(int argc, char *argv[]) {
	int nRanks, nIter, myRank, opt, rc, i;
	double startTime, totalTime, *wallTimes, buf;
	MPI_Status unused;
	
	setbuf(stdout, NULL);
	nIter = 10;
	/*  http://www.opengroup.org/onlinepubs/000095399/functions/getopt.html */
    while ((opt = getopt(argc, argv, "n:")) != -1) {
   		if (opt == 'n') {
			nIter = atoi(optarg);
        }
        else {
	        printf("Unrecognized option %c\n\n", optarg);
			return -1;
        }
	}
	if (MPI_Init(&argc, &argv) != MPI_SUCCESS) {
        printf("ERROR: problem with MPI_Init\n");
    }
	MPI_Comm_rank(MPI_COMM_WORLD, &myRank);
	MPI_Comm_size(MPI_COMM_WORLD, &nRanks);
	if (nRanks <= 2) {
		printf("Two or less processes really defeats the purpose of this test!\n");
		MPI_Finalize();
		return -2;
	}
	/* Check # of iterations around the ring */
	if (nIter > 1000) {
		printf("Sorry, more than 1000 iterations not supported!\n");
		MPI_Finalize();
		return -3;
	}
	/* Allocate space for nIter doubles */
	if (myRank == 0) {
		if ((wallTimes = malloc(nIter * sizeof(double))) == NULL) {
			printf("Couldn't allocate memory for %d doubles!\n", nIter);
			MPI_Finalize();
			return -4;
		}
	}
    /* printf ("Number of tasks = %d My rank = %d\n", nRanks, myRank); */
	
	buf = 123456.987654;
	for (i = 0; i < nIter; i++) {
		/* synchronize! */
		MPI_Barrier(MPI_COMM_WORLD);
		/* Root initiates the send and hangs until it receives from the last rank */
		if (myRank == 0) {
			/* Send to the next rank */
			startTime = MPI_Wtime();
			MPI_Send(&buf, 1, MPI_DOUBLE, myRank+1, 0, MPI_COMM_WORLD);
			MPI_Recv(&buf, 1, MPI_DOUBLE, nRanks-1, 0, MPI_COMM_WORLD, &unused);
			*(wallTimes + i) = MPI_Wtime() - startTime;
			printf("#Finished iteration %d in %6.3f seconds\n", i+1, *(wallTimes + i));
		}
		/* Last rank sends to the root */
		else if (myRank == (nRanks - 1)) {
			MPI_Recv(&buf, 1, MPI_DOUBLE, myRank-1, 0, MPI_COMM_WORLD, &unused);
			MPI_Send(&buf, 1, MPI_DOUBLE, 0, 0, MPI_COMM_WORLD);
		}
		/* All other ranks receive from previous rank and send to the next rank */
		else {
			MPI_Recv(&buf, 1, MPI_DOUBLE, myRank-1, 0, MPI_COMM_WORLD, &unused);
			MPI_Send(&buf, 1, MPI_DOUBLE, myRank+1, 0, MPI_COMM_WORLD);
		}
	}
	
	/* Print average wall time */
	if (myRank == 0) {
		totalTime = 0;
		for (i = 0; i < nIter; i++) {
			totalTime += *(wallTimes + i);
		}
		printf("#%d processes, %d iterations, total time: %6.3f secs, average round trip time per iteration: %6.3f secs\n", nRanks, nIter, totalTime, totalTime / nIter);
		printf("success\n");
		free(wallTimes);
	}
	MPI_Finalize();
	return 0;
}
